package communication.definitions;

import communication.exceptions.InvalidArgumentsException;
import communication.exceptions.InvalidFunctionException;

import device.Device;

/**
 * A <code>Message</code> object is used to package functions and commands into
 * a format which is simple for the dispatcher to interpret.
 * <p>
 * When a command on the robot need to be called a new message object should be
 * created with all the relevent information and given to the communicator.
 * <p>
 * When a command is received by the communicator, it will turn the bytes into a
 * new message object and send it to the dispatcher.
 * 
 * @author Peter Kalauskas
 */
public class Message {
	private DeviceType device;
	private byte id;
	private Function function;
	private byte[] data;

	/**
	 * Constructs a new Message from only bytes. This constructor can only be
	 * called to construct a new message which has been received. This is
	 * because the bytes specified for the function are assumed to represent a
	 * <code>ReceivedFunction</code>.
	 * <p>
	 * This constructor is mainly for constructing a <code>Message</code> from
	 * bytes which are received by the communicator.
	 * 
	 * @param device
	 *            the bytes representing the device.
	 * @param id
	 *            the id of the device.
	 * @param receivedFunction
	 *            the bytes representing the <code>ReceivedFunction</code>.
	 * @param data
	 *            the data which was sent along with the function. Size will be
	 *            checked.
	 * @throws InvalidArgumentsException
	 *             if data.length is greater than or less than the number of
	 *             arguments the <code>ReceivedFunction</code> expected. This
	 *             will never be thrown if the function expects an array.
	 * @throws InvalidFunctionException
	 *             if the bytes given for the <code>ReceivedFunction</code> do
	 *             not correspond to the bytes of any listed function.
	 */
	public Message(byte device, byte id, byte receivedFunction, byte[] data)
			throws InvalidArgumentsException, InvalidFunctionException {
		this.device = DeviceType.valueOf(device);
		this.id = id;
		this.function = ReceivedFunction.valueOf(receivedFunction);
		this.data = data;

		safetyCheck();
	}

	/**
	 * Constructs a new Message from a <code>Device</code> object and a
	 * <code>Function</code> enumeration.
	 * 
	 * @param d
	 *            the device to be stored in this message.
	 * @param f
	 *            the function to be stored in this message.
	 * @param data
	 *            the data to be passed along with this message.
	 * @throws InvalidArgumentsException
	 *             if data.length is greater than or less than the number of
	 *             arguments the <code>Function</code> expected. This will never
	 *             be thrown if the function expects an array.
	 */
	public Message(Device d, Function f, byte[] data)
			throws InvalidArgumentsException {
		this.device = d.getType();
		this.id = d.getID();
		this.function = f;
		this.data = data;

		safetyCheck();
	}

	/**
	 * Checks the number of arguments being passed in this message along with
	 * this function. An exception will be thrown if there is a mismatch.
	 * 
	 * @throws InvalidArgumentsException
	 *             if data.length is greater than or less than the number of
	 *             arguments the <code>Function</code> expected. This will never
	 *             be thrown if the function expects an array.
	 */
	private void safetyCheck() throws InvalidArgumentsException {
		if (getFunction().arguments() != Function.ARRAY) {
			if (getFunction().arguments() < getData().length) {
				throw new InvalidArgumentsException(
						"Too many arguments in message \"" + getDevice() + ","
								+ getId() + "," + getFunction() + " ");
			} else if (getFunction().arguments() > getData().length) {
				throw new InvalidArgumentsException(
						"Too few arguments in message \"" + getDevice() + ","
								+ getId() + "," + getFunction() + " ");
			}
		}
	}

	/**
	 * Returns the <code>DeviceType</code> used for this message.
	 * 
	 * @return the <code>DeviceType</code> for this message.
	 */
	public DeviceType getDevice() {
		return device;
	}

	/**
	 * Returns the <code>DeviceType</code> used for this message.
	 * 
	 * @return the <code>DeviceType</code> for this message.
	 */
	public byte getId() {
		return id;
	}

	/**
	 * Returns the <code>DeviceType</code> used for this message.
	 * 
	 * @return the <code>DeviceType</code> for this message.
	 */
	public Function getFunction() {
		return function;
	}

	/**
	 * Returns the <code>DeviceType</code> used for this message.
	 * 
	 * @return the <code>DeviceType</code> for this message.
	 */
	public byte[] getData() {
		return data;
	}

	/**
	 * Puts the Message into a printable format.
	 * <p>
	 * <code>
	 * <pre>
	 * DeviceType(ID) -> Function (arg0,arg1 ... arg)
	 * </pre>
	 * </code>
	 * 
	 * @return the <code>DeviceType</code> for this message.
	 */
	public String toString() {
		String s = device + "(" + id + ") -> " + function + "(";
		for (int i = 0; i < data.length; i++) {
			s += data[i];
			if (i != data.length - 1) {
				s += ",";
			}
		}
		return s + ")";
	}
}
